/*

 Programa que roda nas máquinas clientes (peers)

 */
package trabalho.pkg3.sd.cliente;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Random;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.discovery.DiscoveryEvent;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.MimeMediaType;
import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.IDFactory;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeMsgEvent;
import net.jxta.pipe.PipeMsgListener;
import net.jxta.pipe.PipeService;
import net.jxta.platform.NetworkConfigurator;
import net.jxta.platform.NetworkManager;
import net.jxta.protocol.DiscoveryResponseMsg;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.util.JxtaBiDiPipe;
import net.jxta.util.JxtaServerPipe;

public class PeerCliente {

    public String nomePeer;
    public int TcpPort = 9722;
    public PeerID PID;
    public File ConfigurationFile;
    public PipeAdvertisement advPipeServidor;
    public PipeAdvertisement advPipeRequisicoes;
    public PipeAdvertisement advPipePortador;
    public ArrayList<File> regArquivos;
    public ArrayList<Long> dataModifArquivos;
    public ArrayList<File> arquivosEmUso;
    public PeerGroup NetPeerGroup;
    public DiscoveryService discService;
    public DescobridorArquivos descArqs;
    public AceitadorDeRequisicoes adr;
    public int numCliente;
    public boolean naoAchouAdvPipeServ = true;
    public boolean encontrouAdvPipeDownload;
    public int numTentativasReqDownloadServidor;
    private String filePath;

    // Inicializa o peer cliente estabelecendo a conexão com o grupo e obtendo a lista de arquivos que este peer possui para compartilhar
    public PeerCliente() {

        try {

            Logger.getLogger("net.jxta").setLevel(Level.SEVERE);
            // Define o nome do peer cliente e seu PID

            Random rd = new Random();
            numCliente = rd.nextInt(10000);
            nomePeer = InetAddress.getLocalHost().getHostName() + "Cli" + numCliente;
            PID = IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID, nomePeer.getBytes());
            ConfigurationFile = new File("." + System.getProperty("file.separator") + nomePeer);
            NetworkManager.RecursiveDelete(ConfigurationFile);

            filePath = "/tmp/DirCompartGrupo02sd/";

            File f = new File(filePath);

            if (!f.exists()) {

                f.mkdir();
            }

            Ferramentas.CheckForExistingConfigurationDeletion(nomePeer, ConfigurationFile);

            // Inicializa, configura e estabelece a conexão do peer na com o servidor
            NetworkManager MyNetworkManager = new NetworkManager(NetworkManager.ConfigMode.EDGE,
                    nomePeer, ConfigurationFile.toURI());

            NetworkConfigurator MyNetworkConfigurator = MyNetworkManager.getConfigurator();

            MyNetworkConfigurator.clearRendezvousSeeds();
//            String TheSeed = "tcp://" + InetAddress.getLocalHost().getHostAddress() + ":" + TcpPort;
            String TheSeed = "tcp://" + "x-men" + ":" + TcpPort;

            MyNetworkConfigurator.addSeedRendezvous(URI.create(TheSeed));
            MyNetworkConfigurator.setTcpEnabled(true);
            MyNetworkConfigurator.setTcpIncoming(true);
            MyNetworkConfigurator.setTcpOutgoing(true);
            MyNetworkConfigurator.setUseMulticast(false);
            MyNetworkConfigurator.setPeerID(PID);

            NetPeerGroup = MyNetworkManager.startNetwork();
            NetPeerGroup.getRendezVousService().setAutoStart(false);

            // Verifica se a conexão foi estabelecida com sucesso
            if (MyNetworkManager.waitForRendezvousConnection(0)) {
                System.out.println("Servidor encontrado.");
            } else {
                System.out.println("Nao foi possivel encontrar o servidor.");
            }

            // Cria a lista de arquivos compartilhados e de arquivos que estão em uso (para a exclusão mútua)
            regArquivos = new ArrayList<File>();
            arquivosEmUso = new ArrayList<File>();
            dataModifArquivos = new ArrayList<Long>();

            // Inicia as threads que verifica os arquivos compartilhados e que aceita requisições
            descArqs = new DescobridorArquivos();
            adr = new AceitadorDeRequisicoes();

            discService = NetPeerGroup.getDiscoveryService();

            while (naoAchouAdvPipeServ) {
                discService.getRemoteAdvertisements(null, DiscoveryService.ADV, "Name", null, 100, new DiscListener());
            }

            // Cira um novo pipe para requisição de arquivo junto ao servidor
            advPipeRequisicoes = getPipeAdvertisementRequisicao();

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        } catch (PeerGroupException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public PipeAdvertisement getPipeAdvertisementRequisicao() {

        PipeAdvertisement advPipeRequisicao = (PipeAdvertisement) AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
        PipeID MyPipeID = IDFactory.newPipeID(PeerGroupID.defaultNetPeerGroupID, nomePeer.getBytes());

        // Cria um novo pipe baseado nas informações do peer
        advPipeRequisicao.setPipeID(MyPipeID);
        advPipeRequisicao.setType(PipeService.UnicastType);
        advPipeRequisicao.setName("Pipe" + nomePeer);

        return advPipeRequisicao;
    }

    public void menu() {

        try {

            String nomeArq;
            String op = "ABC";
            Scanner scn = new Scanner(System.in);

            // "Publica" o advertisement do pipe
            discService.publish(advPipeRequisicoes, DiscoveryService.INFINITE_LIFETIME, DiscoveryService.NO_EXPIRATION);
            discService.remotePublish(advPipeRequisicoes);

            adr.start();
            descArqs.start();

            while (op.charAt(0) != '0') {

                System.out.println("\n********* Peer Cliente " + nomePeer + " **********");

                System.out.println("\nEscolha a opcao desejada: \n (1)Listar arquivos existentes na rede \n (2)Obter arquivo \n (3)Usar arquivo \n (4)Liberar arquivo \n (0)Sair \n Opcao escolhida: ");
                op = scn.next();

                switch (op.charAt(0)) {

                    // Caso deseja-se verificar os arquivos compartilhados na rede
                    case '1':

                        listarArquivos();

                        break;


                    // Caso deseja-se fazer o download de algum arquivo
                    case '2':

                        numTentativasReqDownloadServidor = 0;

                        System.out.println("Entre com o nome do arquivo que deseja obter: ");
                        nomeArq = scn.next();

                        downloadArquivo(nomeArq);

                        break;

                    // Informa-se qual arquivo será utilizado pelo peer: Compõe a exclusão mútua
                    case '3':

                        String nomeArqUso;

                        System.out.println("Arquivos presentes no diretorio compartilhado: ");

                        for (File f : regArquivos) {

                            System.out.println(f.getName());
                        }

                        System.out.println("Entre com o nome do arquivo que sera utilizado: ");
                        nomeArqUso = scn.next();

                        // Verifica na lista de arquivos compartilhados qual arquivo o peer irá utilizar
                        for (File f : regArquivos) {

                            if (f.getName().equals(nomeArqUso)) {

                                // Define esse arquivo como "em uso"
                                arquivosEmUso.add(f);
                            }
                        }

                        break;

                    // Informa-se qual arquivo deseja liberar para um possível acesso de outro peer: Compõe a exclusão mútua
                    case '4':

                        String nomeArqDesuso;

                        // Lista ao cliente quais são os arquivos que estão em uso no momento
                        System.out.println("Arquivos em uso no momento: ");
                        for (File f : arquivosEmUso) {

                            System.out.println(f.getName());
                        }

                        // O usuário entra com o nome do arquvio que deseja liberar o acesso
                        System.out.println("Entre com o nome do arquivo que sera liberado: ");
                        nomeArqDesuso = scn.next();
                        File arqDesuso = null;
                        for (File f : arquivosEmUso) {

                            if (f.getName().equals(nomeArqDesuso)) {

                                // Remove o arquivo informado da lista de arquivos em uso
                                arqDesuso = f;
                            }
                        }

                        if (arqDesuso != null) {

                            arquivosEmUso.remove(arqDesuso);
                        }

                        break;

                    // Encerra o programa do cliente
                    case '0':

                        System.out.println("Obrigado por utilizar o sistema.");

                        break;
                }
            }

            System.exit(0);
        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void listarArquivos() {

        try {

            // Configura a mensagem que será enviada ao servidor solicitando o download de um arquivo
            Message mensagemServidor = new Message();
            StringMessageElement comandoServidor = new StringMessageElement(
                    "ComandoServidor", "ListarArquivos", null);

            StringMessageElement peerRemetente = new StringMessageElement(
                    "PeerRemetente", nomePeer, null);

            StringMessageElement nomeArquivo = new StringMessageElement(
                    "NomeArquivo", "null", null);

            // Compõe a nova mensagem
            mensagemServidor.addMessageElement(comandoServidor);
            mensagemServidor.addMessageElement(peerRemetente);
            mensagemServidor.addMessageElement(nomeArquivo);

            // Estabelece o pipe de comunicação
            JxtaBiDiPipe biDiPipeServidor = new JxtaBiDiPipe(NetPeerGroup, advPipeServidor, null);
            biDiPipeServidor.setMessageListener(new RecebedorMensagemPipeCliente(biDiPipeServidor));

            if (biDiPipeServidor.isBound()) {

                // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
                biDiPipeServidor.sendMessage(mensagemServidor);
            }

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void downloadArquivo(String nomeArq) {

        try {

            // Configura a mensagem que será enviada ao servidor solicitando o download de um arquivo
            Message mensagemServidor = new Message();
            StringMessageElement comandoServidor = new StringMessageElement(
                    "ComandoServidor", "DownloadArquivo", null);

            StringMessageElement peerRemetente = new StringMessageElement(
                    "PeerRemetente", nomePeer, null);

            StringMessageElement nomeArquivo = new StringMessageElement(
                    "NomeArquivo", nomeArq, null);

            // Compõe a nova mensagem
            mensagemServidor.addMessageElement(comandoServidor);
            mensagemServidor.addMessageElement(peerRemetente);
            mensagemServidor.addMessageElement(nomeArquivo);

            // Estabelece o pipe de comunicação
            JxtaBiDiPipe biDiPipeServidor = new JxtaBiDiPipe(NetPeerGroup, advPipeServidor, new MsgListenerDownload(nomeArq));

            if (biDiPipeServidor.isBound()) {

                // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
                biDiPipeServidor.sendMessage(mensagemServidor);
            }

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public void informarNovoArquivo(File f) {

        try {

            // Configura a mensagem que será enviada ao servidor notificando a inclusão de um novo arquivo
            Message mensagemServidor = new Message();
            StringMessageElement comandoServidor = new StringMessageElement(
                    "ComandoServidor", "NovoArquivo", null);

            StringMessageElement peerRemetente = new StringMessageElement(
                    "PeerRemetente", nomePeer, null);

            StringMessageElement nomeArquivo = new StringMessageElement(
                    "NomeArquivo", f.getName(), null);

            StringMessageElement tamanhoArquivo = new StringMessageElement(
                    "TamanhoArquivo", Long.toString(f.length()), null);

            StringMessageElement dataModifArquivo = new StringMessageElement(
                    "DataModifArquivo", Long.toString(f.lastModified()), null);

            // Compõe a nova mensagem
            mensagemServidor.addMessageElement(comandoServidor);
            mensagemServidor.addMessageElement(peerRemetente);
            mensagemServidor.addMessageElement(nomeArquivo);
            mensagemServidor.addMessageElement(tamanhoArquivo);
            mensagemServidor.addMessageElement(dataModifArquivo);

            JxtaBiDiPipe biDiPipeServidor = new JxtaBiDiPipe(NetPeerGroup, advPipeServidor, null);
            biDiPipeServidor.setMessageListener(new RecebedorMensagemPipeCliente(biDiPipeServidor));

            if (biDiPipeServidor.isBound()) {

                // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
                biDiPipeServidor.sendMessage(mensagemServidor);
            }

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void informarAtualizacaoDeArquivo(File f) {
        try {
            // Configura a mensagem que será enviada ao servidor notificando a inclusão de um novo arquivo
            Message mensagemServidor = new Message();
            StringMessageElement comandoServidor = new StringMessageElement(
                    "ComandoServidor", "AtualizacaoArquivo", null);

            StringMessageElement peerRemetente = new StringMessageElement(
                    "PeerRemetente", nomePeer, null);

            StringMessageElement nomeArquivo = new StringMessageElement(
                    "NomeArquivo", f.getName(), null);

            StringMessageElement tamanhoArquivo = new StringMessageElement(
                    "TamanhoArquivo", Long.toString(f.length()), null);

            StringMessageElement dataModifArquivo = new StringMessageElement(
                    "DataModifArquivo", Long.toString(f.lastModified()), null);

            // Compõe a nova mensagem
            mensagemServidor.addMessageElement(comandoServidor);
            mensagemServidor.addMessageElement(peerRemetente);
            mensagemServidor.addMessageElement(nomeArquivo);
            mensagemServidor.addMessageElement(tamanhoArquivo);
            mensagemServidor.addMessageElement(dataModifArquivo);

            JxtaBiDiPipe biDiPipeServidor = new JxtaBiDiPipe(NetPeerGroup, advPipeServidor, null);
            biDiPipeServidor.setMessageListener(new RecebedorMensagemPipeCliente(biDiPipeServidor));

            // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
            if (biDiPipeServidor.isBound()) {

                biDiPipeServidor.sendMessage(mensagemServidor);
            }

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public void informarRemocaoDeArquivo(File f) {
        try {
            // Configura a mensagem que será enviada ao servidor notificando a inclusão de um novo arquivo
            Message mensagemServidor = new Message();
            StringMessageElement comandoServidor = new StringMessageElement(
                    "ComandoServidor", "RemocaoArquivo", null);

            StringMessageElement peerRemetente = new StringMessageElement(
                    "PeerRemetente", nomePeer, null);

            StringMessageElement nomeArquivo = new StringMessageElement(
                    "NomeArquivo", f.getName(), null);

            // Compõe a nova mensagem
            mensagemServidor.addMessageElement(comandoServidor);
            mensagemServidor.addMessageElement(peerRemetente);
            mensagemServidor.addMessageElement(nomeArquivo);

            JxtaBiDiPipe biDiPipeServidor = new JxtaBiDiPipe(NetPeerGroup, advPipeServidor, null);
            biDiPipeServidor.setMessageListener(new RecebedorMensagemPipeCliente(biDiPipeServidor));

            if (biDiPipeServidor.isBound()) {

                // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
                biDiPipeServidor.sendMessage(mensagemServidor);
            }

        } catch (IOException ex) {
            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public class MsgListenerDownload implements PipeMsgListener {

        public String nomeArq;

        public MsgListenerDownload(String nomeArq) {

            this.nomeArq = nomeArq;
        }

        @Override
        public void pipeMsgEvent(PipeMsgEvent pipeMsgEvent) {

            // Obtém a mensagem enviada pelo peer que possui o arquivo
            Message mensagemRecebida = pipeMsgEvent.getMessage();
            String respostaAoRequisitante = mensagemRecebida.getMessageElement("RespostaAoRequisitante").toString();

            // Caso essa mensagem seja de que o arquivo não está em uso
            if (respostaAoRequisitante.equals("ArquivoDisponivel")) {

                int numTentativaConexao = 1;

                // Obtém o nome do peer que possui o arquivo
                String nomePortador = mensagemRecebida.getMessageElement("PeerPortador").toString();
                Message mensagemDownload = new Message();

                System.out.println("Arquivo disponivel em " + nomePortador + ". O download sera requisitado.");

                encontrouAdvPipeDownload = false;

                DiscListenerDownload discListenerDownload = new DiscListenerDownload(nomePortador);

                // Obtém o advertisement do peer que possui o aquivo
                while (!encontrouAdvPipeDownload) {

                    discService.getRemoteAdvertisements(null, DiscoveryService.ADV, "Name", null, 100, discListenerDownload);
                }

                System.out.println("Tentativa de conexao " + numTentativaConexao++ + "...");

                try {

                    requsitaDownloadAoPortador(mensagemDownload, nomeArq, nomePortador);

                } catch (IOException ex) {

                    System.out.println("Erro ao estabelecer conexao com " + nomePortador + ".");

                    System.out.println("Tentativa de conexao " + numTentativaConexao++ + "...");

                    try {

                        requsitaDownloadAoPortador(mensagemDownload, nomeArq, nomePortador);

                    } catch (IOException ex1) {

                        System.out.println("Erro ao estabelecer conexao com " + nomePortador + ".");

                        System.out.println("Tentativa de conexao " + numTentativaConexao++ + "...");

                        try {

                            requsitaDownloadAoPortador(mensagemDownload, nomeArq, nomePortador);

                        } catch (IOException ex2) {

                            System.out.println("Erro ao estabelecer conexao com " + nomePortador + ". Numero maximo de tentativas atingido. Por favor, tente novamente.");
                        }
                    }

                }

                // Caso a resposta não seja de confirmação da disponibilidade do arquivo
            } else if (respostaAoRequisitante.equals("ArquivoNaoEncotrado")) {

                System.out.println("Arquivo nao encontrado. Verifique se o nome foi digitado corretamente.");

            } else {

                if (numTentativasReqDownloadServidor < 3) {
                    
                    numTentativasReqDownloadServidor++;
                    Ferramentas.GoToSleep(5);
                    
                    downloadArquivo(nomeArq);
                
                } else {
                    
                    System.out.println("Arquivo nao disponivel no momento. Tente novamente mais tarde.");
                }
            }
        }
    }

    void requsitaDownloadAoPortador(Message mensagemDownload, String nomeArq, String nomePortador) throws IOException {


        // Estabelece um piper de comunicação com o peer que possui o arquivo
        JxtaBiDiPipe biDiPipeDownload = new JxtaBiDiPipe(NetPeerGroup, advPipePortador, null);

        // Configura a mensagem que será enviada ao servidor notificando a inclusão de um novo arquivo
        StringMessageElement comandoPortador = new StringMessageElement(
                "ComandoPortador", "DownloadArquivo", null);

        StringMessageElement peerRemetente = new StringMessageElement(
                "PeerRemetente", nomePeer, null);

        StringMessageElement nomeArquivo = new StringMessageElement(
                "NomeArquivo", nomeArq, null);

        // Compõe a nova mensagem
        mensagemDownload.addMessageElement(comandoPortador);
        mensagemDownload.addMessageElement(peerRemetente);
        mensagemDownload.addMessageElement(nomeArquivo);

        // Envia a mensagem ao servidor, notificando a inclusão de um novo arquivo
        biDiPipeDownload.sendMessage(mensagemDownload);

        long inicioTempoDownload = System.currentTimeMillis();

        biDiPipeDownload.setMessageListener(new MsgListenerFinal(nomeArq, inicioTempoDownload));

        System.out.println("Download do arquivo " + nomeArq + " requisitado. Por favor, aguarde.");

    }

    public class MsgListenerFinal implements PipeMsgListener {

        public String nomeArq;
        public long inicioTempoDownload;

        public MsgListenerFinal(String nomeArq, long inicioTempoDownload) {

            this.nomeArq = nomeArq;
            this.inicioTempoDownload = inicioTempoDownload;
        }

        @Override
        public void pipeMsgEvent(PipeMsgEvent event) {

            FileOutputStream fos = null;

            try {

                Message mensagemRecebida = event.getMessage();

                byte[] bytesRecebidos = new byte[(int) mensagemRecebida.getMessageElement("ConteudoArquivo").getByteLength()];

                // Armazena o arquivo recebido com o mesmo nome que veio do outro peer
                fos = new FileOutputStream(filePath + nomeArq);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                InputStream is = mensagemRecebida.getMessageElement("ConteudoArquivo").getStream();

                is.read(bytesRecebidos);

                fos.write(bytesRecebidos);

                fos.flush();
                fos.close();

                long finalTempoDownload = System.currentTimeMillis();

                System.out.println("Arquivo " + nomeArq + " (" + (int) mensagemRecebida.getMessageElement("ConteudoArquivo").getByteLength() + " bytes) recebido com sucesso. Tempo total de download: " + (finalTempoDownload - inicioTempoDownload) + "ms.");

            } catch (IOException ex) {
                Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);

            } finally {
                try {
                    fos.close();
                } catch (IOException ex) {
                    Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }
    }

    public class DiscListener implements DiscoveryListener {

        @Override
        public void discoveryEvent(DiscoveryEvent discEvent) {

            if (!naoAchouAdvPipeServ) {
                return;
            }

            PipeAdvertisement advPipeTemp;

            DiscoveryResponseMsg discMsg = discEvent.getResponse();

            if (discMsg != null) {

                Enumeration<Advertisement> TheEnumeration = discMsg.getAdvertisements();

                while (TheEnumeration.hasMoreElements() && naoAchouAdvPipeServ) {

                    advPipeTemp = (PipeAdvertisement) TheEnumeration.nextElement();

                    // Obtém o advertisement do servidor
                    if (advPipeTemp.getName().equals("PipeServidor")) {

                        naoAchouAdvPipeServ = false;
                        PeerCliente.this.advPipeServidor = advPipeTemp;

                        return;
                    }

                }
            }
        }
    }

    public class DiscListenerDownload implements DiscoveryListener {

        public String nomePortador;

        public DiscListenerDownload(String nomePortador) {

            this.nomePortador = nomePortador;
        }

        @Override
        public void discoveryEvent(DiscoveryEvent discEvent) {

            PipeAdvertisement advPipeTemp;

            DiscoveryResponseMsg discMsg = discEvent.getResponse();

            if (discMsg != null) {

                Enumeration<Advertisement> TheEnumeration = discMsg.getAdvertisements();

                while (TheEnumeration.hasMoreElements() && !encontrouAdvPipeDownload) {

                    advPipeTemp = (PipeAdvertisement) TheEnumeration.nextElement();

                    // Obtém o advertisement do pipe do peer que possui o arquivo requisitado
                    if (advPipeTemp.getName().equals("Pipe" + nomePortador)) {

                        encontrouAdvPipeDownload = true;
                        advPipePortador = advPipeTemp;

                        return;
                    }

                }
            }
        }
    }

    public class DescobridorArquivos extends Thread {

        @Override
        public void run() {

            while (true) {

                // Atribui uma pasta que sera usada no compartilhamento de arquivos
                File diretorioCompartilhado = new File(filePath);

                // Obtém a lista de arquivos contidos na pasta compartilhada
                File[] listaArquivos = diretorioCompartilhado.listFiles();

                // Verifica se um arquivo novo foi adicionado ou se algum arquivo foi atualizado

                if (listaArquivos != null) {

                    File arqAtualizado = null;

                    for (File f : listaArquivos) {

                        // Quando um novo arquivo é encontrado
                        if (!regArquivos.contains(f) && f.getName().charAt(0) != '.') {

                            regArquivos.add(f);
                            dataModifArquivos.add(regArquivos.indexOf(f), Long.valueOf(f.lastModified()));
                            informarNovoArquivo(f);
                        }

                        // Quando um arquivo é atualziado
                        if (regArquivos.contains(f) && f.lastModified() > dataModifArquivos.get(regArquivos.indexOf(f))) {

                            arqAtualizado = f;
                        }
                    }

                    if (arqAtualizado != null) {

                        dataModifArquivos.set(regArquivos.indexOf(arqAtualizado), Long.valueOf(arqAtualizado.lastModified()));
                        informarAtualizacaoDeArquivo(arqAtualizado);
                    }

                    boolean flag;

                    File arqRemovido = null;

                    // Verifica se algum arquivo foi excluído do peer
                    for (File f : regArquivos) {

                        flag = false;

                        for (File g : listaArquivos) {

                            if (f.equals(g)) {
                                flag = true;
                            }
                        }

                        if (!flag) {

                            arqRemovido = f;
                        }
                    }

                    if (arqRemovido != null) {

                        dataModifArquivos.remove(regArquivos.indexOf(arqRemovido));
                        regArquivos.remove(arqRemovido);

                        if (arquivosEmUso.contains(arqRemovido)) {

                            arquivosEmUso.remove(arqRemovido);
                        }

                        informarRemocaoDeArquivo(arqRemovido);
                    }

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
    }

    public class AceitadorDeRequisicoes extends Thread {

        @Override
        // Thread que roda esperando novas requisições ao peer
        public void run() {
            try {

                JxtaServerPipe pipeRequisicoes = new JxtaServerPipe(NetPeerGroup, advPipeRequisicoes);
                pipeRequisicoes.setPipeTimeout(0);

                while (true) {

                    JxtaBiDiPipe biDiPipeConexao = pipeRequisicoes.accept();

                    if (biDiPipeConexao != null) {

                        AtendimentoRequisicaoCliente arc = new AtendimentoRequisicaoCliente(biDiPipeConexao);
                        arc.start();
                    }
                }
            } catch (IOException ex) {
                Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public class AtendimentoRequisicaoCliente extends Thread {

        JxtaBiDiPipe biDiPipeConexao;

        public AtendimentoRequisicaoCliente(JxtaBiDiPipe biDiPipeConexao) {

            this.biDiPipeConexao = biDiPipeConexao;
        }

        @Override
        // Thread que aguarda a mensagem de requisição
        public void run() {

            biDiPipeConexao.setMessageListener(new RecebedorMensagemPipeCliente(biDiPipeConexao));
        }
    }

    public class RecebedorMensagemPipeCliente implements PipeMsgListener {

        JxtaBiDiPipe biDiPipeConexao;

        public RecebedorMensagemPipeCliente(JxtaBiDiPipe biDiPipeConexao) {

            this.biDiPipeConexao = biDiPipeConexao;
        }

        @Override
        public synchronized void pipeMsgEvent(PipeMsgEvent pipeMsgEvent) {

            FileInputStream fis = null;
            try {

                Message mensagemRecebida = pipeMsgEvent.getMessage();
                Message mensagemEnviada = new Message();
                String comandoPortador = mensagemRecebida.getMessageElement("ComandoPortador").toString();

                switch (comandoPortador.charAt(0)) {

                    case 'T':

                        File arqRequisitado = null;

                        String nomeArquivo = mensagemRecebida.getMessageElement("NomeArquivo").toString();

                        // Verifica qual é o arquivo requititado na mensagem
                        for (File f : regArquivos) {

                            if (f.getName().equals(nomeArquivo)) {

                                arqRequisitado = f;
                            }
                        }

                        StringMessageElement respostaPortador;

                        // Caso o arquivo não esteja em uso, envia ao servidor a mensagem de que o arquivo está disponível
                        if (!arquivosEmUso.contains(arqRequisitado)) {

                            respostaPortador = new StringMessageElement(
                                    "RespostaPortador", "ArquivoDisponivel", null);

                            // Caso o arquivo esteja em uso, envia ao servidor a mensagem de que o arquivo não está disponível
                        } else {

                            respostaPortador = new StringMessageElement(
                                    "RespostaPortador", "ArquivoNaoDisponivel", null);
                        }

                        // Compõe a mensagem de resposta
                        mensagemEnviada.addMessageElement(respostaPortador);

                        try {
                            biDiPipeConexao.sendMessage(mensagemEnviada);
                        } catch (IOException ex) {
                            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        break;


                    case 'D':

                        File arqEnviado = null;
                        String nomeArqEnviado = mensagemRecebida.getMessageElement("NomeArquivo").toString();
                        String peerRemetente = mensagemRecebida.getMessageElement("PeerRemetente").toString();

                        // Seleciona o arquivo solicitado
                        for (File f : regArquivos) {

                            if (f.getName().equals(nomeArqEnviado)) {

                                arqEnviado = f;
                                System.out.println("Preparando-se para enviar " + f.getName() + " para "
                                        + peerRemetente + ".");
                            }
                        }

                        // Configura o envio do arquivo solitado
                        byte[] bytesEnviados = new byte[(int) arqEnviado.length()];
                        fis = new FileInputStream(arqEnviado);
                        BufferedInputStream bis = new BufferedInputStream(fis);
                        bis.read(bytesEnviados, 0, bytesEnviados.length);

                        // Envia o arquivo solicitado ao peer solicitador
                        ByteArrayMessageElement conteudoArquivo = new ByteArrayMessageElement("ConteudoArquivo", MimeMediaType.AOS, bytesEnviados, null);
                        mensagemEnviada.addMessageElement(conteudoArquivo);

                        try {
                            biDiPipeConexao.sendMessage(mensagemEnviada);
                            System.out.println(arqEnviado.getName() + " enviado para "
                                    + peerRemetente + ".");
                        } catch (IOException ex) {
                            Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        break;


                    case 'A':

                        // Caso a mensagem recebida seja de que um arquivo que o peer possui foi atualizado, executa a operação de download do novo arquivo
                        String nomeArquivoAtualizado = mensagemRecebida.getMessageElement("NomeArquivo").toString();

                        File arqAtualizado = null;

                        for (File f : regArquivos) {

                            if (f.getName().equals(nomeArquivoAtualizado)) {

                                arqAtualizado = f;
                                System.out.println("Nova versao de " + f.getName() + " detectada. Sera feita uma tentativa de atualizacao. Caso algum erro ocorra, solicite o download do arquivo manualmente.");
                            }
                        }

                        if (arqAtualizado != null) {

                            dataModifArquivos.remove(regArquivos.indexOf(arqAtualizado));
                            regArquivos.remove(arqAtualizado);

                            if (arquivosEmUso.contains(arqAtualizado)) {

                                arquivosEmUso.remove(arqAtualizado);
                            }

                            arqAtualizado.delete();
                        }

                        downloadArquivo(nomeArquivoAtualizado);

                        break;

                    case 'R':

                        // Para a recepção de respostas do servidor a comandos enviados
                        String respostaDoServidor = mensagemRecebida.getMessageElement("RespostaAoRequisitante").toString();
                        System.out.println(respostaDoServidor);

                        break;

                }

            } catch (IOException ex) {
                Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ex) {
                    Logger.getLogger(PeerCliente.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }
    }
}
